// GraphLinkManager.h: Manager for graph links
//
//////////////////////////////////////////////////////////////////////
#ifndef __GRAPHLINKMANAGER_H__
#define __GRAPHLINKMANAGER_H__

#include "GraphStructures.h"

class CGraphLinkManager
{
public:
	enum { BUCKET_SIZE_SHIFT = 7 };
	enum { BUCKET_SIZE = 1 << BUCKET_SIZE_SHIFT };

	CGraphLinkManager();
	~CGraphLinkManager();

	void Clear();

	unsigned CreateLink();
	void DestroyLink(unsigned linkIndex);

	unsigned GetNextNode(unsigned linkId) const
	{
		return GetBidirectionalData(linkId)->directionalData[IsReversed(linkId)].nextNodeIndex;
	}
	void SetNextNode(unsigned linkId, unsigned nodeIndex)
	{
		GetBidirectionalData(linkId)->directionalData[IsReversed(linkId)].nextNodeIndex = nodeIndex;
	}

	unsigned GetPrevNode(unsigned linkId) const
	{
		// Here we assume that there are only two types of links - 0 (direct) and 1 (reversed)
		return GetBidirectionalData(linkId)->directionalData[IsReversed(linkId ^ 1)].nextNodeIndex;
	}

	unsigned GetNextLink(unsigned linkId) const
	{
		return GetBidirectionalData(linkId)->directionalData[IsReversed(linkId)].nextLinkIndex;
	}
	void SetNextLink(unsigned linkId, unsigned nextLink)
	{
		GetBidirectionalData(linkId)->directionalData[IsReversed(linkId)].nextLinkIndex = nextLink;
	}

	void SetMaxWaterDepth(unsigned linkId, float depth)
	{
		depth *= 100.f;
		Limit<float>(depth, -std::numeric_limits<short>::max(), std::numeric_limits<short>::max());
		GraphLinkBidirectionalData::DirectionalData& reversedLinkData = GetBidirectionalData(linkId)->directionalData[eLT_ReversedLink];
		reversedLinkData.waterDepthInCmLow = ((int)depth) & 0xFF;
		reversedLinkData.waterDepthInCmHigh = ((int)depth) >> 8;
	}
	void SetMinWaterDepth(unsigned linkId, float depth)
	{
		depth *= 100.f;
		Limit<float>(depth, -std::numeric_limits<short>::max(), std::numeric_limits<short>::max());
		GraphLinkBidirectionalData::DirectionalData& directLinkData = GetBidirectionalData(linkId)->directionalData[eLT_DirectLink];
		directLinkData.waterDepthInCmLow = ((int)depth) & 0xFF;
		directLinkData.waterDepthInCmHigh = ((int)depth) >> 8;
	}
	float GetMaxWaterDepth(unsigned linkId) const
	{
		const GraphLinkBidirectionalData::DirectionalData& reversedLinkData = GetBidirectionalData(linkId)->directionalData[eLT_ReversedLink];
		return (signed((reversedLinkData.waterDepthInCmHigh << 8) | reversedLinkData.waterDepthInCmLow)) * 0.01f;
	}
	float GetMinWaterDepth(unsigned linkId) const
	{
		const GraphLinkBidirectionalData::DirectionalData& directLinkData = GetBidirectionalData(linkId)->directionalData[eLT_DirectLink];
		return (signed((directLinkData.waterDepthInCmHigh << 8) | directLinkData.waterDepthInCmLow)) * 0.01f;
	}

	/// Use this when setting up the radius during creation
	void SetRadius(unsigned linkId, float radius);

	float GetRadius(unsigned linkId) const {
		return GetBidirectionalData(linkId)->directionalData[IsReversed(linkId)].maxRadiusInCm * 0.01f;
	}
	float GetOrigRadius(unsigned linkId) const
	{
		return GetBidirectionalData(linkId)->directionalData[IsReversed(linkId)].origMaxRadiusInCm * 0.01f;
	}

	/// Use this when modifying the radius during game
	void ModifyRadius(unsigned linkId, float radius)
	{
		int radiusInCm = (int)(radius * 100.f);
		Limit<int>(radiusInCm, -std::numeric_limits<short>::max(), std::numeric_limits<short>::max());
		GetBidirectionalData(linkId)->directionalData[IsReversed(linkId)].maxRadiusInCm = radiusInCm;
	}

	/// Indicates if a link has been changed from its original value
	bool IsLinkModified(unsigned linkId) const
	{
		return fabsf(GetRadius(linkId) - GetOrigRadius(linkId)) > 0.01f;
	}

	/// Restores the link radius to its original value
	void RestoreLink(unsigned linkId)
	{
		GraphLinkBidirectionalData::DirectionalData& dd = GetBidirectionalData(linkId)->directionalData[IsReversed(linkId)];
		dd.maxRadiusInCm = dd.origMaxRadiusInCm;
	}

	bool IsNewLink(unsigned linkId) const
	{
		return (fabsf(GetRadius(linkId) + 2.5f) < 2.49f);
	}

	unsigned int GetStartIndex(unsigned linkId) const
	{
		return GetBidirectionalData(linkId)->directionalData[IsReversed(linkId)].startIndex;
	}
	void SetStartIndex(unsigned linkId, unsigned int index);
	unsigned int GetEndIndex(unsigned linkId) const
	{
		return GetBidirectionalData(linkId)->directionalData[IsReversed(linkId)].endIndex;
	}
	void SetEndIndex(unsigned linkId, unsigned int index);

	void SetEdgeCenter(unsigned linkId, const Vec3& center)
	{
		GetBidirectionalData(linkId)->vEdgeCenter = center;
	}
	Vec3& GetEdgeCenter(unsigned linkId)
	{
		return GetBidirectionalData(linkId)->vEdgeCenter;
	}
	const Vec3& GetEdgeCenter(unsigned linkId) const
	{
		return GetBidirectionalData(linkId)->vEdgeCenter;
	}

	void SetExposure(unsigned linkId, float fExposure)
	{
		GetBidirectionalData(linkId)->fExposure = fExposure;
	}
	float GetExposure(unsigned linkId) const
	{
		return GetBidirectionalData(linkId)->fExposure;
	}

	// NOTE Apr 26, 2007: <pvl> "simplicity" of a link makes sense only with
	// human waypoint links.  For criterion of simplicity see
	// CWaypointHumanNavRegion::CheckWaypointLinkWalkability().
	unsigned IsSimple(unsigned linkId) const
	{
		return GetBidirectionalData(linkId)->directionalData[eLT_DirectLink].simplePassabilityCheck;
	}
	void SetSimple(unsigned linkId, bool linear)
	{
		GraphLinkBidirectionalData::DirectionalData* pDD = GetBidirectionalData(linkId)->directionalData;
		pDD[0].simplePassabilityCheck = linear;
		pDD[1].simplePassabilityCheck = true;
	}

	bool SimplicityComputed(unsigned linkId) const
	{
		return GetBidirectionalData(linkId)->directionalData[eLT_ReversedLink].simplePassabilityCheck;
	}

private:
	// The low bit of the pointer is a flag - it must be masked out.
	GraphLinkBidirectionalData* GetBidirectionalData(unsigned linkId);
	const GraphLinkBidirectionalData* GetBidirectionalData(unsigned linkId) const;

	bool IsReversed(unsigned linkId) const
	{
		return (linkId & 1) != 0;
	}

	std::vector<GraphLinkBidirectionalData*> m_buckets;
	unsigned m_firstFreeLinkIndex;
};

inline CGraphLinkManager::CGraphLinkManager()
:	m_firstFreeLinkIndex(0)
{
}

inline CGraphLinkManager::~CGraphLinkManager()
{
	Clear();
}

inline void CGraphLinkManager::Clear()
{
	for (int i = 0, count = int(m_buckets.size()); i < count; ++i)
	{
		delete [] m_buckets[i];
	}
	m_buckets.clear();

	m_firstFreeLinkIndex = 0;
}

inline void CGraphLinkManager::SetRadius(unsigned linkId, float radius)
{
	int radiusInCm = (int)(radius * 100.f);
	Limit<int>(radiusInCm, -std::numeric_limits<short>::max(), std::numeric_limits<short>::max());
	// NOTE Jul 27, 2007: <pvl> radius being 0.0 seems to have a special interpretation,
	// don't let it go to zero (i.e. completely impassable) just because of
	// quantization - set it to 1cm instead.  This prevents graph consistency
	// checking from getting confused and flagging false errors.
	if ((radiusInCm == 0) && (radius != 0.f))
	{
		radiusInCm = (radius > 0.f);
	}

	GraphLinkBidirectionalData::DirectionalData& dd = GetBidirectionalData(linkId)->directionalData[IsReversed(linkId)];
	dd.maxRadiusInCm = radiusInCm;
	dd.origMaxRadiusInCm = radiusInCm;
}


inline void CGraphLinkManager::SetStartIndex(unsigned linkId, unsigned int index)
{
	assert(index < 3 && index >= 0);
	GetBidirectionalData(linkId)->directionalData[IsReversed(linkId)].startIndex = index;
}

inline void CGraphLinkManager::SetEndIndex(unsigned linkId, unsigned int index)
{
	assert(index < 3 && index >= 0);
	GetBidirectionalData(linkId)->directionalData[IsReversed(linkId)].endIndex = index;
}

inline GraphLinkBidirectionalData* CGraphLinkManager::GetBidirectionalData(unsigned linkId)
{
	unsigned bidirectionalIndex = (linkId >> 1) - 1;
	return &m_buckets[bidirectionalIndex >> BUCKET_SIZE_SHIFT][bidirectionalIndex & (BUCKET_SIZE - 1)];
}

inline const GraphLinkBidirectionalData* CGraphLinkManager::GetBidirectionalData(unsigned linkId) const
{
	unsigned bidirectionalIndex = (linkId >> 1) - 1;
	return &m_buckets[bidirectionalIndex >> BUCKET_SIZE_SHIFT][bidirectionalIndex & (BUCKET_SIZE - 1)];
}

inline unsigned CGraphLinkManager::CreateLink()
{
	if (!m_firstFreeLinkIndex)
	{
		m_buckets.push_back(new GraphLinkBidirectionalData[BUCKET_SIZE]);
		GraphLinkBidirectionalData* pLinks = m_buckets.back();
		pLinks[0].directionalData[eLT_DirectLink].nextLinkIndex = m_firstFreeLinkIndex;
		unsigned nextLink = m_firstFreeLinkIndex;
		for (int i = 1; i < BUCKET_SIZE; ++i)
		{
			pLinks[i].directionalData[eLT_DirectLink].nextLinkIndex = nextLink;
			nextLink = ((((m_buckets.size() - 1) << BUCKET_SIZE_SHIFT) + i) + 1) << 1;
		}
		m_firstFreeLinkIndex = nextLink;
	}

	unsigned link = m_firstFreeLinkIndex;
	GraphLinkBidirectionalData::DirectionalData& directLinkData = GetBidirectionalData(link)->directionalData[eLT_DirectLink];
	m_firstFreeLinkIndex = directLinkData.nextLinkIndex;
	directLinkData.nextLinkIndex = 0;
	return link;
}

inline void CGraphLinkManager::DestroyLink(unsigned linkIndex)
{
	linkIndex = linkIndex & ~1;
	if (GraphLinkBidirectionalData* pData = GetBidirectionalData(linkIndex))
	{
		*pData = GraphLinkBidirectionalData();
		pData->directionalData[eLT_DirectLink].nextLinkIndex = m_firstFreeLinkIndex;
		m_firstFreeLinkIndex = linkIndex;
	}
}

#endif //__GRAPHLINKMANAGER_H__
